﻿Imports System.Net
Imports System.Xml
Imports Kirishima16.Net.Twitter

Partial Class TwitterClient
    Implements ITwitterClient
    Private Const StrInvalidUserListTypeReservated As String = "不正なUserListTypeが指定されました。"
    Private Enum UserListType
        Friends
        Followers
    End Enum

    ''' <summary>
    ''' すべてのページから自分がフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks></remarks>
    Public Function GetFriends() As IList(Of TwitterUser) Implements ITwitterClient.GetFriends
        Return GetFriends(0, 0)
    End Function

    ''' <summary>
    ''' ページを指定して自分がフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <param name="beginPage">取得開始ページ</param>
    ''' <param name="endPage">取得終了ページ</param>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks>
    ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
    ''' </remarks>
    Public Function GetFriends(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser) Implements ITwitterClient.GetFriends
        Return GetUsers(beginPage, endPage, UserListType.Friends)
    End Function

    ''' <summary>
    ''' すべてのページから自分をフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <returns>ユーザーのコレクション</returns>
    ''' <remarks></remarks>
    Public Function GetFollowers() As IList(Of TwitterUser) Implements ITwitterClient.GetFollowers
        Return GetFollowers(0, 0)
    End Function

    ''' <summary>
    ''' ページを指定して自分をフォローしているユーザーのコレクションを取得します。
    ''' </summary>
    ''' <param name="beginPage">取得開始ページ</param>
    ''' <param name="endPage">取得終了ページ</param>
    ''' <returns>フォローしているユーザーのコレクション</returns>
    ''' <remarks>
    ''' beginPageまたはendPageが0以下の場合はそれぞれ無視されます。
    ''' </remarks>
    Public Function GetFollowers(ByVal beginPage As Integer, ByVal endPage As Integer) As IList(Of TwitterUser) Implements ITwitterClient.GetFollowers
        Return GetUsers(beginPage, endPage, UserListType.Followers)
    End Function

    Private Function GetUsers(ByVal beginPage As Integer, ByVal endPage As Integer, ByVal type As UserListType) As IList(Of TwitterUser)
        If Not IsLogined Then
            Throw New InvalidOperationException(StrThisInstanceIsNotLoginedYet)
        End If
        If beginPage > endPage And endPage > 0 Then
            Throw New ArgumentOutOfRangeException("endPage", "endPageには0以下またはbeginPage以上の値のみ指定できます。")
        End If

        Dim lstUsers As New List(Of TwitterUser)
        Dim pageNo As Integer
        If (beginPage > 0) Then
            pageNo = beginPage
        Else
            pageNo = 1
        End If
        While endPage = 0 OrElse pageNo <= endPage
            Dim url As String
            Select Case type
                Case UserListType.Friends
                    url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFriendsPath, pageNo)
                Case UserListType.Followers
                    url = String.Format("{0}{1}?page={2}", My.Resources.TwitterUrl, My.Resources.TwitterFollowersPath, pageNo)
                Case Else
                    Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
            End Select
            Dim req As HttpWebRequest = DirectCast(WebRequest.Create(url), HttpWebRequest)
            req.SetTwitterGetHeaders(Nothing, _cookies)
            Dim xmlRes As XmlDocument = Nothing
            Using res As HttpWebResponse = DirectCast(req.GetResponse(), HttpWebResponse)
                Select Case res.StatusCode
                    Case HttpStatusCode.OK
                        res.LoadCookies(_cookies)
                        xmlRes = res.GetResponseAsXHtml()
                    Case Else
                        Return Nothing
                End Select
            End Using
            Dim xnm As XmlNamespaceManager = xmlRes.GetXHtmlNameSpaceManager("x")
            Dim nodes As XmlNodeList = xmlRes.SelectNodes("//x:tr[starts-with(@id,'user_')]", xnm)
            For Each node As XmlNode In nodes
                Dim user As TwitterUser = ParseUser(node, xnm, "x", type)
                lstUsers.Add(user)
            Next
            If nodes.Count = 0 Then
                Exit While
            End If
            Dim nodesNextLink As XmlNodeList = xmlRes.SelectNodes("//x:div[@id='pagination']/x:a[contains(@rel,'next')]", xnm)
            If (nodesNextLink.Count > 0) Then
                pageNo += 1
            Else
                Exit While
            End If
        End While
        Return lstUsers
    End Function

    ''' <summary>
    ''' レスポンスに含まれるユーザー情報を解析します。
    ''' </summary>
    ''' <param name="node">ユーザーのルートノード</param>
    ''' <param name="xnm">ノードに関連づけられたXmlNamespaceManager</param>
    ''' <param name="prefix">プレフィックス</param>
    ''' <param name="type">ユーザーリストの種類</param>
    ''' <returns>解析されたユーザー情報</returns>
    ''' <remarks></remarks>
    Private Shared Function ParseUser(ByVal node As XmlNode, ByVal xnm As XmlNamespaceManager, ByVal prefix As String, ByVal type As UserListType) As TwitterUser
        Dim user As TwitterUser
        user = New TwitterUser()
        user.UserID = CLng(node.Attributes("id").Value.Substring(5))
        Dim nodeAddress As XmlNode
        Select Case type
            Case UserListType.Friends
                nodeAddress = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:address[@class='about vcard']", prefix), xnm)
            Case UserListType.Followers
                nodeAddress = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:address[@class='about vcard']", prefix), xnm)
            Case Else
                Throw New ArgumentException(StrInvalidUserListTypeReservated, "type")
        End Select
        Dim nodeScreenName As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span[@class='label screenname']/{0}:a", prefix), xnm)
        user.Language = nodeScreenName.Attributes("hreflang").Value
        user.UserName = nodeScreenName.InnerText
        Dim nodeFullName As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span/{0}:span[@class='label fullname']", prefix), xnm)
        If nodeFullName IsNot Nothing Then
            user.DisplayName = nodeFullName.InnerText
        End If
        Dim nodeLocation As XmlNode = nodeAddress.SelectSingleNode(String.Format("{0}:span/{0}:span[@class='location']", prefix), xnm)
        If nodeLocation IsNot Nothing Then
            user.Location = nodeLocation.InnerText
            If nodeFullName IsNot Nothing Then
                user.Location = user.Location.Substring(3)
            End If
        End If
        Dim nodeThumb As XmlNode = node.SelectSingleNode(String.Format("{0}:td[@class='thumb vcard']/{0}:a/{0}:img", prefix), xnm)
        user.IconUrl = nodeThumb.Attributes("src").Value
        ' NOTICE:自己紹介は取得できなくなりました。
        Dim nodeCurrently As XmlNode = node.SelectSingleNode(String.Format("{0}:td[@class='user-detail']/{0}:span[@class='user-body']/{0}:span[@class='currently']", prefix), xnm)
        If nodeCurrently IsNot Nothing Then
            user.Currently = nodeCurrently.InnerText.TrimStart()
        End If
        user.IsProtected = node.Attributes("class").Value.Contains("protected")
        Select Case type
            Case UserListType.Friends
                user.IsFriend = True
                user.IsFollower = node.Attributes("class").Value.Contains("direct-message-able")
            Case UserListType.Followers
                user.IsFollower = True
                user.IsFriend = node.Attributes("class").Value.Contains("following")
        End Select
        Return user
    End Function
End Class